GUID: 2653889r GitHub URL: https://github.com/Hrisity/HrisityAI.github.io.git
The following code snippets are part of a process that generates chorales using Recurrent Neural Networks (RNNs). I will break down each piece of code, so their function can be understood:
import tensorflow as tf
tf.keras.utils.get_file(
"jsb_chorales.tgz",
"https://github.com/ageron/data/raw/main/jsb_chorales.tgz",
cache_dir=".",
extract=True)
Downloading data from https://github.com/ageron/data/raw/main/jsb_chorales.tgz 117793/117793 [==============================] - 0s 0us/step
'./datasets/jsb_chorales.tgz'
This first bit of code is used to download and extract a dataset. With ‘import tensorflow as tf’ the TensorFlow library, which is a popular machine learning framework, is imported. Then, the function ‘tf.keras.utils.get_file(…)’ helps in downloading a file from a URL. ‘jsb_chorales.tgz’ is the name of the file, and the URL afterwards is where the is downloaded from. The ‘cache_dir=“.”’ is specifying where the file will be stored, as ‘“.”’ means the current directory. The last line indicates that the downloaded file should be automatically extracted.
from pathlib import Path
jsb_chorales_dir = Path("datasets/jsb_chorales")
train_files = sorted(jsb_chorales_dir.glob("train/chorale_*.csv"))
valid_files = sorted(jsb_chorales_dir.glob("valid/chorale_*.csv"))
test_files = sorted(jsb_chorales_dir.glob("test/chorale_*.csv"))
In this step, the data files within the dataset are both located and sorted. ‘from pathlib import Path’ aims to import ‘Path’ class from the ‘pathlib’ module for filesystem path operations. Then, a ‘Path’ object that leads to the directory where the chorales datasets are located is created. The ‘glob(…)’ method is put in place to find the files matching a certain pattern. In this case, the variables: ‘train_files’, ‘valid_files’, and ‘testfiles’ store lists of paths to the training, validation, and test datasets, respectively. The chorale*.csv pattern selects CSV files matching this pattern in each subdirectory (‘train’, ‘valid’, and ‘test’).
import pandas as pd
def load_chorales(filepaths):
return [pd.read_csv(filepath).values.tolist() for filepath in filepaths]
train_chorales = load_chorales(train_files)
valid_chorales = load_chorales(valid_files)
test_chorales = load_chorales(test_files)
Here, the aim is to load the CSV files into a format that is suitable for the RNN. First, the pandas library, usually used for data manipulation and analysis, is imported. The function ‘load_chorales(filepaths)’ is defined to read the chorale files., while ‘pd.read_csv(filepath).values.tolist()’ reads the CSV files into a pandas DataFrame. Then, converts it into a NumPy array with ‘.values’, and later to a list with .tolist(). In brief, the function returns a list of lists. Each inner list represents a chorale. At the end of this bit of code, the variables are used to store the loaded chorales for training, validation, and testing.
The following 4 snippets of code are part of the"Preparing the Data" stage.
notes = set()
for chorales in (train_chorales, valid_chorales, test_chorales):
for chorale in chorales:
for chord in chorale:
notes |= set(chord)
n_notes = len(notes)
min_note = min(notes - {0}) #0 denotes no notes being played
max_note = max(notes)
assert min_note == 36
assert max_note == 81
This bit of code creates a set of all unique notes present in the dataset. First, it creates a set to store the notes. Then, with the ‘ notes |= set(chord)’ the notes from each chord are added to the set. ’n_notes’ shows the total number of unique notes, while ‘min_note’ and ‘max_note’ calculate the minimum and maximum note values, excluding the value 0. It denotes silence. The assertions, in the end, check if the range of notes is as expected.
The following cell is code for a synthesiser to play MIDI. Not part of machine learning code to generate Bach, but useful for listening to the results and samples used for training!
from IPython.display import Audio
import numpy as np
def notes_to_frequencies(notes):
# Frequency doubles when you go up one octave; there are 12 semi-tones
# per octave; Note A on octave 4 is 440 Hz, and it is note number 69.
return 2 ** ((np.array(notes) - 69) / 12) * 440
def frequencies_to_samples(frequencies, tempo, sample_rate):
note_duration = 60 / tempo # the tempo is measured in beats per minutes
# To reduce click sound at every beat, we round the frequencies to try to
# get the samples close to zero at the end of each note.
frequencies = (note_duration * frequencies).round() / note_duration
n_samples = int(note_duration * sample_rate)
time = np.linspace(0, note_duration, n_samples)
sine_waves = np.sin(2 * np.pi * frequencies.reshape(-1, 1) * time)
# Removing all notes with frequencies ≤ 9 Hz (includes note 0 = silence)
sine_waves *= (frequencies > 9.).reshape(-1, 1)
return sine_waves.reshape(-1)
def chords_to_samples(chords, tempo, sample_rate):
freqs = notes_to_frequencies(chords)
freqs = np.r_[freqs, freqs[-1:]] # make last note a bit longer
merged = np.mean([frequencies_to_samples(melody, tempo, sample_rate)
for melody in freqs.T], axis=0)
n_fade_out_samples = sample_rate * 60 // tempo # fade out last note
fade_out = np.linspace(1., 0., n_fade_out_samples)**2
merged[-n_fade_out_samples:] *= fade_out
return merged
def play_chords(chords, tempo=160, amplitude=0.1, sample_rate=44100, filepath=None):
samples = amplitude * chords_to_samples(chords, tempo, sample_rate)
if filepath:
from scipy.io import wavfile
samples = (2**15 * samples).astype(np.int16)
wavfile.write(filepath, sample_rate, samples)
return display(Audio(filepath))
else:
return display(Audio(samples, rate=sample_rate))
## testing the synthesiser
for index in range(3):
play_chords(train_chorales[index])